package com.arashivision.wifisdk;

import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

/**
 * Created by vans on 4/6/18.
 */

public class WifiSdk
{
    private static final String TAG = "OneWifiSdk";
    private WifiSdkImplement mImpl;
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private boolean mReleased;
    private Looper mLooper;

    // execute task on Looper which have no return value
    private void syncExecute(final Runnable runnable) {
        if(mReleased)
            throw new RuntimeException("WifiSdk released");
        final Throwable[] throwables = new Throwable[1];
        final TaskWaiter taskWaiter = new TaskWaiter();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch(Throwable t) {
                    throwables[0] = t;
                }
                taskWaiter.done();
            }
        });
        taskWaiter.await();
        if(throwables[0] != null) {
            Log.e(TAG, "[WifiSdk] WifiSdkImplement execute error: " + throwables[0]);
            throwables[0].printStackTrace();
            throw new RuntimeException(throwables[0]);
        }
    }


    // execute task on Looper which have return value
    private interface Task<T> {
        T run();
    }

    private <T> T syncExecuteTask(final Task task) {
        if(mReleased)
            throw new RuntimeException("WifiSdk released");
        final Object[] result = new Object[2];
        final TaskWaiter taskWaiter = new TaskWaiter();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    result[1] = task.run();
                } catch(Throwable t) {
                    result[0] = t;
                }
                taskWaiter.done();
            }
        });
        taskWaiter.await();
        if(result[0] != null) {
            Log.e(TAG, "[WifiSdk] WifiSdkImplement execute error: " + result[0]);
            ((Throwable)result[0]).printStackTrace();
            throw new RuntimeException((Throwable)result[0]);
        }
        return (T)result[1];
    }

    /**
     * Construct WifiSdk instance.
     * @param context could be application context
     * @param callbacks callback
     * @param callbackHandler callback will post this handler to execute
     */
    public WifiSdk(final Context context,
                     final WifiSdkCallback callbacks, final Handler callbackHandler) {
        mHandlerThread = new HandlerThread("WifiSdk");
        mHandlerThread.start();
        mLooper = mHandlerThread.getLooper();
        mHandler = new Handler(mHandlerThread.getLooper());
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl = new WifiSdkImplement(mLooper, context, callbacks, callbackHandler);
            }
        });
    }

    /**
     * Open wifi
     */
    public synchronized void openWifi() {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.openWifi();
            }
        });
    }

    /**
     * Close Wifi.
     */
    public synchronized void closeWifi() {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.closeWifi();
            }
        });
    }

    private static String stackTraceToString(StackTraceElement[] stackTraceElements) {
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement element : stackTraceElements) {
            sb.append(element.toString());
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * Release instance.
     * <p>All resource will be released.</p>
     * <p>After released, you can't use the instance</p>
     */
    public synchronized void release() {
        Log.i(TAG, "release OneCamera: \n" + stackTraceToString(Thread.currentThread().getStackTrace()));
        if(mReleased)
            return;

        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.release();
            }
        });
        mHandlerThread.quit();
        try {
            mHandlerThread.join();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        mHandlerThread = null;
        mReleased = true;
    }

    @Override
    protected void finalize() throws Throwable
    {
        if (!mReleased)
            release();
        super.finalize();
    }

    /**
     * set the debug state,default is false
     * @param state debug state
     */
    public synchronized void setEnableDebug(final boolean state)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.setEnableDebug(state);
            }
        });
    }

    /**
     * start wifi scan for scanDurationMs
     * @param scanDurationMs wifi scan duration > 0
     */
    public synchronized void startScan(final long scanDurationMs)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.startScan(scanDurationMs);
            }
        });
    }

    /**
     * stop scan, advise to stop scan before connect to save cpu
     */
    public synchronized void stopScan()
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.stopScan();
            }
        });
    }

    /**
     * get wifi state
     * @return true: open , false: close
     */
    public synchronized boolean getWifiState()
    {
        final boolean[] res = new boolean[1];
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                res[0] = mImpl.getWifiState();
            }
        });
        return res[0];
    }

    /**
     * get current connect wifi info
     * @return
     */

    public synchronized WifiInfo getConnectWifiInfo()
    {
        final WifiInfo[] res = new WifiInfo[1];
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                res[0] = mImpl.getConnectWifiInfo();
            }
        });
        return res[0];
    }

    /**
     * forget connected wifi in the history
     * @param result wifi cell
     */
    public synchronized void forgetWifiResult(final ScanResult result)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.forgetWifiResult(result);
            }
        });
    }

    /**
     * change wifi pwd,normall need connect after change wifi pwd
     * @param result wifi cell
     * @param newPwd password
     */
    public synchronized void changePwd(final ScanResult result,final String newPwd)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.changePwd(result,newPwd);
            }
        });
    }

    /**
     * check whether result{@link ScanResult} is the current connected one
     * @param result wifi cell
     * @return true or false
     */
    public synchronized boolean isConnectWifi(final ScanResult result)
    {
        final boolean[] res = new boolean[1];
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                res[0] = mImpl.isConnectWifi(result);
            }
        });
        return res[0];
    }

    /**
     * connect new wifi with password
     * @param result wifi cell
     * @param pwd password
     */
    public synchronized void connectWithPwd(final ScanResult result,final String pwd)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.connectWithPwd(result,pwd);
            }
        });
    }

    /**
     * connect wifi with resultP{@link ScanResult}
     * @param result wifi cell
     */
    public synchronized void connect(final ScanResult result)
    {
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                mImpl.connect(result);
            }
        });
    }

    /**
     * calculate wifi signal with your own numLevels
     * eg. if you set numLevels to 5, you will get 1 -5 as return
     * @param rssi rssi value get from {@link ScanResult#level}
     * @param numLevels maximum for rssi
     * @return converted value calculate according to numLevels
     */
    public synchronized int calculateSignal(final int rssi, final int numLevels)
    {
        final int[] ret = new int[1];
        syncExecute(new Runnable() {
            @Override
            public synchronized void run() {
                ret[0] = mImpl.calculateSignal(rssi,numLevels);
            }
        });
        return ret[0];
    }
}
